/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.grizzly.http.jk.common;

import com.sun.grizzly.http.jk.core.JkChannel;
import com.sun.grizzly.http.jk.core.JkHandler;
import com.sun.grizzly.http.jk.core.Msg;
import com.sun.grizzly.http.jk.core.MsgContext;
import com.sun.grizzly.http.jk.core.WorkerEnv;
import com.sun.grizzly.http.jk.util.threads.ThreadPool;
import com.sun.grizzly.http.jk.util.threads.ThreadPoolRunnable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.management.ObjectName;


import com.sun.grizzly.tcp.Request;
import com.sun.grizzly.tcp.RequestGroupInfo;
import com.sun.grizzly.tcp.RequestInfo;
import com.sun.grizzly.util.LoggerUtils;
import java.net.URLEncoder;
import java.util.logging.Level;
import org.apache.commons.modeler.Registry;

/** Pass messages using unix domain sockets.
 *
 * @author Costin Manolache
 */
public class ChannelUn extends JniHandler implements JkChannel  {

    static final int CH_OPEN = 4;
    static final int CH_CLOSE = 5;
    static final int CH_READ = 6;
    static final int CH_WRITE = 7;
    String file;
    ThreadPool tp = ThreadPool.createThreadPool(true);

    /* ==================== Tcp socket options ==================== */
    public ThreadPool getThreadPool() {
        return tp;
    }

    public void setFile(String f) {
        file = f;
    }

    public String getFile() {
        return file;
    }
    /* ==================== ==================== */
    int socketNote = 1;
    int isNote = 2;
    int osNote = 3;
    int localId = 0;

    public void init() throws IOException {
        if (file == null) {
            LoggerUtils.getLogger().log(Level.FINEST, "No file, disabling unix channel");
            return;
        //throw new IOException( "No file for the unix socket channel");
        }
        if (wEnv != null && wEnv.getLocalId() != 0) {
            localId = wEnv.getLocalId();
        }

        if (localId != 0) {
            file = file + localId;
        }
        File socketFile = new File(file);
        if (!socketFile.isAbsolute()) {
            String home = wEnv.getJkHome();
            if (home == null) {
                LoggerUtils.getLogger().log(Level.FINEST, "No jkhome");
            } else {
                File homef = new File(home);
                socketFile = new File(homef, file);
                LoggerUtils.getLogger().log(Level.FINEST, "Making the file absolute " + socketFile);
            }
        }

        if (!socketFile.exists()) {
            try {
                FileOutputStream fos = new FileOutputStream(socketFile);
                fos.write(1);
                fos.close();
            } catch (Throwable t) {
                LoggerUtils.getLogger().log(Level.SEVERE, "Attempting to create the file failed, disabling channel" + socketFile);
                return;
            }
        }
        // The socket file cannot be removed ...
        if (!socketFile.delete()) {
            LoggerUtils.getLogger().log(Level.SEVERE, "Can't remove socket file " + socketFile);
            return;
        }


        super.initNative("channel.un:" + file);

        if (apr == null || !apr.isLoaded()) {
            LoggerUtils.getLogger().log(Level.FINEST, "Apr is not available, disabling unix channel ");
            apr = null;
            return;
        }

        // Set properties and call init.
        setNativeAttribute("file", file);
        // unixListenSocket=apr.unSocketListen( file, 10 );

        setNativeAttribute("listen", "10");
        // setNativeAttribute( "debug", "10" );

        // Initialize the thread pool and execution chain
        if (next == null && wEnv != null) {
            if (nextName != null) {
                setNext(wEnv.getHandler(nextName));
            }
            if (next == null) {
                next = wEnv.getHandler("dispatch");
            }
            if (next == null) {
                next = wEnv.getHandler("request");
            }
        }

        super.initJkComponent();
        JMXRequestNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "requestNote");
        // Run a thread that will accept connections.
        if (this.domain != null) {
            try {
                tpOName = new ObjectName(domain + ":type=ThreadPool,name=" +
                        getChannelName());

                Registry.getRegistry(null, null).registerComponent(tp, tpOName, null);

                rgOName = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getChannelName());
                Registry.getRegistry(null, null).registerComponent(global, rgOName, null);
            } catch (Exception e) {
                LoggerUtils.getLogger().log(Level.SEVERE, "Can't register threadpool");
            }
        }
        tp.start();
        AprAcceptor acceptAjp = new AprAcceptor(this);
        tp.runIt(acceptAjp);
        LoggerUtils.getLogger().info("JK: listening on unix socket: " + file);

    }
    ObjectName tpOName;
    ObjectName rgOName;
    RequestGroupInfo global = new RequestGroupInfo();
    int count = 0;
    int JMXRequestNote;

    public void start() throws IOException {
    }

    public void destroy() throws IOException {
        if (apr == null) {
            return;
        }
        try {
            if (tp != null) {
                tp.shutdown();
            }

            //apr.unSocketClose( unixListenSocket,3);
            super.destroyJkComponent();

            if (tpOName != null) {
                Registry.getRegistry(null, null).unregisterComponent(tpOName);
            }
            if (rgOName != null) {
                Registry.getRegistry(null, null).unregisterComponent(rgOName);
            }
        } catch (Exception e) {
            LoggerUtils.getLogger().log(Level.SEVERE, "Error in destroy", e);
        }
    }

    public void registerRequest(Request req, MsgContext ep, int count) {
        if (this.domain != null) {
            try {

                RequestInfo rp = req.getRequestProcessor();
                rp.setGlobalProcessor(global);
                ObjectName roname = new ObjectName(getDomain() + ":type=RequestProcessor,worker=" +
                        getChannelName() + ",name=JkRequest" + count);
                ep.setNote(JMXRequestNote, roname);

                Registry.getRegistry(null, null).registerComponent(rp, roname, null);
            } catch (Exception ex) {
                LoggerUtils.getLogger().log(Level.WARNING, "Error registering request");
            }
        }
    }

    /** Open a connection - since we're listening that will block in
    accept
     */
    public int open(MsgContext ep) throws IOException {
        // Will associate a jk_endpoint with ep and call open() on it.
        // jk_channel_un will accept a connection and set the socket info
        // in the endpoint. MsgContext will represent an active connection.
        return super.nativeDispatch(ep.getMsg(0), ep, CH_OPEN, 1);
    }

    public void close(MsgContext ep) throws IOException {
        super.nativeDispatch(ep.getMsg(0), ep, CH_CLOSE, 1);
    }

    public int send(Msg msg, MsgContext ep)
            throws IOException {
        return super.nativeDispatch(msg, ep, CH_WRITE, 0);
    }

    public int receive(Msg msg, MsgContext ep)
            throws IOException {
        int rc = super.nativeDispatch(msg, ep, CH_READ, 1);

        if (rc != 0) {
            LoggerUtils.getLogger().log(Level.SEVERE, "receive error:   " + rc, new Throwable());
            return -1;
        }

        msg.processHeader();

        if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) {
            LoggerUtils.getLogger().log(Level.FINEST, "receive:  total read = " + msg.getLen());
        }

        return msg.getLen();
    }

    public int flush(Msg msg, MsgContext ep) throws IOException {
        return OK;
    }

    public boolean isSameAddress(MsgContext ep) {
        return false; // Not supporting shutdown on this channel.
    }
    boolean running = true;

    /** Accept incoming connections, dispatch to the thread pool
     */
    void acceptConnections() {
        if (apr == null) {
            return;
        }

        if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) {
            LoggerUtils.getLogger().log(Level.FINEST, "Accepting ajp connections on " + file);
        }

        while (running) {
            try {
                MsgContext ep = this.createMsgContext();

                // blocking - opening a server connection.
                int status = this.open(ep);
                if (status != 0 && status != 2) {
                    LoggerUtils.getLogger().log(Level.SEVERE, "Error acceptin connection on " + file);
                    break;
                }

                //    if( LoggerUtils.getLogger().isLoggable(Level.FINEST) )
                //     LoggerUtils.getLogger().log(Level.FINEST,"Accepted ajp connections ");

                AprConnection ajpConn = new AprConnection(this, ep);
                tp.runIt(ajpConn);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    /** Process a single ajp connection.
     */
    void processConnection(MsgContext ep) {
        if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) {
            LoggerUtils.getLogger().log(Level.FINEST, "New ajp connection ");
        }
        try {
            MsgAjp recv = new MsgAjp();
            while (running) {
                int res = this.receive(recv, ep);
                if (res < 0) {
                    // EOS
                    break;
                }
                ep.setType(0);
                LoggerUtils.getLogger().log(Level.FINEST, "Process msg ");
                int status = next.invoke(recv, ep);
            }
            if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) {
                LoggerUtils.getLogger().log(Level.FINEST, "Closing un channel");
            }
            try {
                Request req = (Request) ep.getRequest();
                if (req != null) {
                    ObjectName roname = (ObjectName) ep.getNote(JMXRequestNote);
                    if (roname != null) {
                        Registry.getRegistry(null, null).unregisterComponent(roname);
                    }
                    req.getRequestProcessor().setGlobalProcessor(null);
                }
            } catch (Exception ee) {
                LoggerUtils.getLogger().log(Level.SEVERE, "Error, releasing connection", ee);
            }
            this.close(ep);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public int invoke(Msg msg, MsgContext ep) throws IOException {
        int type = ep.getType();

        switch (type) {
            case JkHandler.HANDLE_RECEIVE_PACKET:
                return receive(msg, ep);
            case JkHandler.HANDLE_SEND_PACKET:
                return send(msg, ep);
            case JkHandler.HANDLE_FLUSH:
                return flush(msg, ep);
        }

        // return next.invoke( msg, ep );
        return OK;
    }

    public String getChannelName() {
        String encodedAddr = "";
        String address = file;
        if (address != null) {
            encodedAddr = "" + address;
            if (encodedAddr.startsWith("/")) {
                encodedAddr = encodedAddr.substring(1);
            }
            encodedAddr = URLEncoder.encode(encodedAddr);
        }
        return ("jk-" + encodedAddr);
    }
}

class AprAcceptor implements ThreadPoolRunnable {

    ChannelUn wajp;

    AprAcceptor(ChannelUn wajp) {
        this.wajp = wajp;
    }

    public Object[] getInitData() {
        return null;
    }

    public void runIt(Object thD[]) {
        wajp.acceptConnections();
    }
}

class AprConnection implements ThreadPoolRunnable {

    ChannelUn wajp;
    MsgContext ep;

    AprConnection(ChannelUn wajp, MsgContext ep) {
        this.wajp = wajp;
        this.ep = ep;
    }

    public Object[] getInitData() {
        return null;
    }

    public void runIt(Object perTh[]) {
        wajp.processConnection(ep);
    }
}
